package org.poem.pic.looklike;

import org.poem.pic.looklike.similarity.ImagePHash;
import org.poem.pic.looklike.vo.FileVo;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.math.BigInteger;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.*;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * @author Asher
 */
public class Calculate {

    /**
     * 可选值为：1,2,4,8,16,32
     * 当值为64时会抛出异常，此时需要实现64位转10进制
     * radix 64 greater than Character.MAX_RADIX
     */
    public static int compareLevel = 4;
    /**
     * 虚线程的线程池
     */
    private static final ExecutorService vcfExecutor = Executors.newVirtualThreadPerTaskExecutor();

    /**
     * 格式化显示文件路径
     *
     * @param str 文件路径
     * @return
     * @throws UnsupportedEncodingException
     */
    private static String formatStr(String str) throws UnsupportedEncodingException {
        return new String(str.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8).replaceAll("C:\\\\Users\\\\admin\\\\Downloads\\\\泰国核销（4.12版本） - 副本", "");
    }

    /**
     * main函数
     * @param args
     */
    public static void main(String[] args) {
        try {
            getFileDistanceWithPHash("C:\\Users\\admin\\Downloads\\泰国核销（4.12版本） - 副本");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    /**
     * 图片加载处理
     * 采用感知hash算法
     *
     * @throws IOException
     */
    public static void getFileDistanceWithPHash(String filePath) throws IOException, ExecutionException, InterruptedException {

        ConcurrentLinkedDeque<FileVo> resultFileName = new ConcurrentLinkedDeque<>();
        ergodic(new File(filePath), resultFileName);
        System.out.println("图片总量:" + resultFileName.size());

        Map<String, List<FileVo>> fileVoMap = new HashMap<>();

        ImagePHash imagePHash = new ImagePHash();
        for (FileVo fileVo : resultFileName) {
            for (FileVo vo : resultFileName) {
                if (fileVo.getFileName().equals(vo.getFileName())) {
                    continue;
                }
                int pearsonBydim = imagePHash.distance(fileVo.getPhash(), vo.getPhash());
                if (pearsonBydim <= 10) {
                    List<FileVo> fileVos = fileVoMap.get(fileVo.getPhash());
                    if (fileVos == null) {
                        fileVos = new ArrayList<>();
                    }
                    boolean save = true;
                    for (FileVo fileVo1 : fileVos) {
                         if (Objects.equals(fileVo1.getFilePath(), vo.getFilePath())) {
                             save = false;
                             break;
                         }
                    }
                    if (save) {
                        fileVos.add(vo);
                        fileVoMap.put(fileVo.getPhash(), fileVos);
                    }
                }
            }
        }

        int count = 0;
        for (String s : fileVoMap.keySet()) {
            List<FileVo> fileVos = fileVoMap.get(s);
                count = count + fileVos.size();
        }

        System.out.println("重复图片数量: " + count);
        System.out.println("有效图片数量: " + (resultFileName.size() - count));
        System.out.println("====================================================");
        int index = 1;
        for (String s : fileVoMap.keySet()) {
            System.out.println("+++++++++++++情形" + index+ "相关内容+++++++++++++");
            List<FileVo> fileVos = fileVoMap.get(s);
            System.out.println("图片指纹数据: " + s + ", 重复数量: " + fileVos.size() + "张");
            int i = 1;
            for (FileVo fileVo : fileVos) {
                System.out.println("第" + i + "张 图片目录及地址: " + formatStr(fileVo.getFilePath()));
                i++;
            }
            index++;
            System.out.println("--------------情形" + index + "完毕--------------");
        }
    }
    /**
     * 图片的相似度
     * 采用图片指纹的方式
     * 皮尔逊相关系数
     *
     * @throws IOException
     */
    public static void getFileDistanceWithDim(String filePath) throws IOException, ExecutionException, InterruptedException {

        ConcurrentLinkedDeque<FileVo> resultFileName = new ConcurrentLinkedDeque<>();
        ergodic(new File(filePath), resultFileName);
        System.out.println("图片总量:" + resultFileName.size());

        Map<List<Double>, List<FileVo>> fileVoMap = new HashMap<>();

        for (FileVo fileVo : resultFileName) {
            for (FileVo vo : resultFileName) {
                if (fileVo.getFileName().equals(vo.getFileName())) {
                    continue;
                }
                Double pearsonBydim = PearsonDemo.getPearsonBydim(fileVo.getPicArrayData(), vo.getPicArrayData());
                //完全相似
                if (pearsonBydim == 1) {
                    List<FileVo> fileVos = fileVoMap.get(fileVo.getPicArrayData());
                    if (fileVos == null) {
                        fileVos = new ArrayList<>();
                    }
                    boolean save = true;
                    for (FileVo fileVo1 : fileVos) {
                        if (Objects.equals(fileVo1.getFilePath(), vo.getFilePath())) {
                            save = false;
                            break;
                        }
                    }
                    if (save) {
                        fileVos.add(vo);
                        fileVoMap.put(fileVo.getPicArrayData(), fileVos);
                    }
                }
            }
        }

        int count = 0;
        for (List<Double> s : fileVoMap.keySet()) {
            List<FileVo> fileVos = fileVoMap.get(s);
            count = count + fileVos.size();
        }

        System.out.println("重复图片数量: " + count);
        System.out.println("有效图片数量: " + (resultFileName.size() - count));
        System.out.println("====================================================");
        int index = 1;
        for (List<Double> s : fileVoMap.keySet()) {
            System.out.println("+++++++++++++情形" + index+ "相关内容+++++++++++++");
            List<FileVo> fileVos = fileVoMap.get(s);
            System.out.println("图片指纹数据: " + s + ", 重复数量: " + fileVos.size() + "张");
            int i = 1;
            for (FileVo fileVo : fileVos) {
                System.out.println("第" + i + "张 图片目录及地址: " + formatStr(fileVo.getFilePath()));
                i++;
            }
            index++;
            System.out.println("--------------情形" + index + "完毕--------------");
        }
    }
    /**
     * 生成md5
     *
     * @param file 图片文件
     * @return MD5值
     * @throws FileNotFoundException
     */
    public static String getStringMd5(File file) throws FileNotFoundException {
        String value = null;
        FileInputStream in = new FileInputStream(file);
        try {
            MappedByteBuffer byteBuffer = in.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.update(byteBuffer);
            BigInteger bi = new BigInteger(1, md5.digest());
            value = bi.toString(16);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != in) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return value;
    }

    /**
     * 获取目录下下的所有文件
     *
     * @param file           文件夹
     * @param resultFileName 目录
     * @return 文件夹下的所有文件
     */
    public static void ergodic(File file, ConcurrentLinkedDeque<FileVo> resultFileName) throws IOException, ExecutionException, InterruptedException {
        File[] files = file.listFiles();
        if (files != null) {
            for (File f : files) {
                vcfExecutor.submit(new Runnable() {
                    /**
                     * Runs this operation.
                     */
                    @Override
                    public void run() {
                        if (f.isDirectory()) {
                            // 判断是否文件夹
                            // 调用自身,查找子目录
                            try {
                                ergodic(f, resultFileName);
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            } catch (ExecutionException e) {
                                throw new RuntimeException(e);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        } else {
                            if (!f.getName().endsWith(".png") && !f.getName().endsWith(".jpg") && !f.getName().endsWith(".jpeg")) {
                                return;
                            }
                            FileVo fileVo = new FileVo();
                            ImagePHash  imagePHash = new ImagePHash();
                            fileVo.setFileName(f.getName());
                            try {
                                fileVo.setFilePath(formatStr(f.getPath()));
                                fileVo.setMd5(getStringMd5(f));
                                fileVo.setPhash(imagePHash.getHash(new FileInputStream(f)));
                                fileVo.setPicArrayData(getPicArrayData(f.getPath()));
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }
                            resultFileName.add(fileVo);
                        }
                    }
                }).get();
            }
        }

    }

    /**
     * 获取图片的指纹数据
     *
     * @param path 图片路径
     * @return 文件的标识码
     * @throws IOException
     */
    public static List<Double> getPicArrayData(String path) throws IOException {
        File file = new File(path);
        if (!file.exists()) {
            throw new RuntimeException(path + ", 文件不存在");
        }
        BufferedImage image = ImageIO.read(file);

        //初始化集合
        final List<Double> picFingerprint = new ArrayList<>(compareLevel * compareLevel * compareLevel);
        IntStream.range(0, compareLevel * compareLevel * compareLevel).forEach(i -> {
            picFingerprint.add(i, 0.0);
        });
        if (image == null) {
            throw new RuntimeException(path + ", 文件不是图片");
        }
        //遍历像素点
        for (int i = 0; i < image.getWidth(); i++) {
            for (int j = 0; j < image.getHeight(); j++) {
                Color color = new Color(image.getRGB(i, j));
                //对像素点进行计算
                putIntoFingerprintList(picFingerprint, color.getRed(), color.getGreen(), color.getBlue());
            }
        }

        return picFingerprint;
    }

    /**
     * 放入像素的三原色进行计算，得到List的位置
     *
     * @param picFingerprintList picFingerprintList
     * @param r                  r
     * @param g                  g
     * @param b                  b
     */
    public static void putIntoFingerprintList(List<Double> picFingerprintList, int r, int g, int b) {
        //比如r g b是126, 153, 200 且 compareLevel为16进制，得到字符串：79c ,然后转10进制，这个数字就是List的位置
        final int index = Integer.valueOf(getBlockLocation(r) + getBlockLocation(g) + getBlockLocation(b), compareLevel);
        final Double origin = picFingerprintList.get(index);
        picFingerprintList.set(index, origin + 1);
    }

    /**
     * w
     * 计算 当前原色应该分在哪个区块
     *
     * @param colorPoint colorPoint
     * @return
     */
    public static String getBlockLocation(int colorPoint) {
        return IntStream.range(0, compareLevel)
                //以10进制计算分在哪个区块
                .filter(i -> {
                    int areaStart = (256 / compareLevel) * i;
                    int areaEnd = (256 / compareLevel) * (i + 1) - 1;
                    return colorPoint >= areaStart && colorPoint <= areaEnd;
                })
                //如果compareLevel大于10则转为对应的进制的字符串
                .mapToObj(location -> compareLevel > 10 ? Integer.toString(location, compareLevel) : location + "")
                .findFirst()
                .orElseThrow();
    }

    public static void putIntoFingerprintMap(Map<Integer, Integer> picFingerprintMap, int r, int g, int b) {
        final int picFingerprint = Integer.valueOf(getBlockLocation(r) + getBlockLocation(g) + getBlockLocation(b), compareLevel);
        Integer value = picFingerprintMap.containsKey(picFingerprint) ? picFingerprintMap.get(picFingerprint) + 1 : 1;
        picFingerprintMap.put(picFingerprint, value);
    }


    public static List<Double> getPicArrayDataByMap(String path) throws IOException {
        BufferedImage bimg = ImageIO.read(new File(path));

        final Map<Integer, Integer> picFingerprintMap = new HashMap<>();

        for (int i = 0; i < bimg.getWidth(); i++) {
            for (int j = 0; j < bimg.getHeight(); j++) {
                //输出一列数据比对
                Color color = new Color(bimg.getRGB(i, j));
                int r = color.getRed();
                int g = color.getGreen();
                int b = color.getBlue();
                putIntoFingerprintMap(picFingerprintMap, r, g, b);
            }
        }

        final List<Integer> keys = picFingerprintMap.keySet().stream().sorted().collect(Collectors.toList());
        final ArrayList<Double> picFingerprintList = new ArrayList<>(keys.size());
        keys.forEach(key -> {
            picFingerprintList.add(Double.valueOf(picFingerprintMap.get(key)));
        });
        return picFingerprintList;
    }


}
